

# Spark 是一个在 JVM 上运行的计算引擎，它的内存管理主要是为了高效地利用 JVM 的堆内存（Heap），
# 同时也能利用一部分堆外内存（Off-Heap）。内存主要服务于两个核心目的：执行计算和缓存数据。

# 预留内存：默认 300M，用来保证系统内部运行（存储一些内部数据结构）

# 用户内存：用户程序使用：map、flatmap，reduce，自己创建的对象数据结构或udf中分配的东西
# 大小占剩下空间的 25% ，静态分配的，不参与spark核心的存储和执行之间的动态调整

# 统一内存：存储内存、执行内存
# 存储内存 (Storage Memory): 用来缓存你 persist() 或 cache() 的数据（RDD, DataFrame/Dataset），加速后续访问。比如你把一个常用的表缓存在内存里。
# 执行内存 (Execution Memory): 用来在任务执行过程中存放临时数据。
#     比如 shuffle（排序、分组、聚合）时产生的中间结果（如 Hash 表、排序缓冲区），join 操作时的临时数据结构，复杂计算时的中间对象等。
# 大小： 占总内存减去预留内存后的 60%
# 存储区和执行区之间没有硬隔离墙！它们共享统一内存这个大池子
# 如果执行任务暂时不需要很多内存，存储就可以多用点来缓存数据
# 如果缓存的数据不多，执行任务就可以多用点内存来处理复杂的计算

# 当执行内存不足时，它可以去占用当前空闲的存储内存部分。但是！ 如果存储内存此时需要更多空间（比如要缓存新数据），而执行内存占用了本该属于存储的内存，
# Spark 会强制驱逐掉部分被缓存的 RDD 数据（根据缓存级别，可能只删内存里的，磁盘备份还在），腾出空间给存储用。被驱逐的数据下次要用就得重新计算或从磁盘读。

# 当存储内存不足时，它也可以去占用当前空闲的执行内存部分。但是！ 如果执行内存此时需要更多空间，而存储内存占用了本该属于执行的内存，
# Spark 不能强行驱逐缓存的数据（因为缓存的数据可能很重要，驱逐影响性能）。
# 执行任务只能等待存储自己释放内存（比如你主动 unpersist() 或者 Spark 根据 LRU 规则自动淘汰旧缓存），或者最终可能因为等太久而失败（OOM）。


# 堆外内存：是 JVM 堆之外的内存  spark.memory.offHeap.size 设置。
# 存储序列化后的缓存数据（更紧凑，节省空间，减少 GC 压力）。
# 存储 Shuffle 过程中的中间数据（特别是 Tungsten 优化后）。

# 不受 JVM 垃圾回收（GC）影响，避免长时间的 GC 停顿导致任务延迟。
# 可以管理更大的内存空间（绕过 JVM 堆大小限制）

# 需要自己管理分配和释放，不如堆内存方便。